<?php
/**
 * Folder (directory) manipulation class.
 *
 * The Folder class can be used for creating, reading and manipulating
 * folders (directories) on the filesystem.
 * Auction:Folder.php
 * @author qqinxl
 * Wed Oct 10 09:06:04 GMT 2007
 * 
 * @category   Auction
 * @package  
 * @copyright  Copyright (c) qqinxl
 */
class File_Folder{
	/**
     * @var string folder
     */
	protected $_folder = null;

	/**
     * @var boolean superuser
     */
	protected $_superUser = false;
	/**
	   * 
	   * @var Zend_Log
	   */
	protected $_logWriter = null;

	const CMD_LS = '/bin/ls';
	const CMD_MKDIR = '/bin/mkdir';
	const CMD_CHOWN = '/bin/chown';
	const CMD_CHMOD = '/bin/chmod';
	const CMD_FILE = '/usr/bin/file';
	const CMD_RMDIR = '/bin/rmdir';
	const CMD_RM = '/bin/rm';
	const CMD_FIND = '/usr/bin/find';
	/**
     * Folder constructor.
     *
	 * @param boolean $superuser superuser access required to read the file
     * @param string folder target folder
     */
	public function __construct($folder, $superUser = false){
		$this->_folder = $folder;
		$this->_superUser = $superUser;
		$this->_logWriter = Zend_Registry::get('log');
	}

	/**
	 * Changes the folder mode.
	 *
	 * Use the standard command-line chmod values.
	 *
	 * @param  string  $mode  the mode of the folder
	 * @return  void
	 */
	public function chmod($mode){
		$this->_logWriter->log('chmod '.$mode,Zend_Log::DEBUG  );
		if (! $this->exists()){
			throw new Exception($this->_folder, SpotSec_Exception::ERROR);
		}

		// Let the chmod command do the validation

		try {
			$shell = System_ShellExec::getInstance();
			if ($shell->execute(self::CMD_CHMOD, $mode . ' ' . $this->_folder, true) != 0){
				throw new Exception($shell->getFirstOutputLine());
			}
		} catch (Exception $e) {
			throw $e;
		}
	}


	/**
	 * Changes the owner and/or group.
	 *
	 * Leave the owner or group blank if you do not want change one or the other.
	 *
	 * @param  string  $owner  folder owner
	 * @param  string  $group  folder group
	 * @param  string  $recursive  do chown recursively
	 * @return  void
	 */
	public function chown($owner, $group, $recursive = false){
		$this->_logWriter->log('chown '.$owner.' '.$group ,Zend_Log::DEBUG  );

		if (! $this->exists()){
			throw new Exception($this->_folder);
		}

		// Let the chown command do the validation

		if ($owner) {
			try {
				if ($recursive){
					$flags = '-R';
				}else{
					$flags = '';
				}
				$shell = System_ShellExec::getInstance();
				if ($shell->execute(self::CMD_CHOWN, $owner . " $flags " . $this->_folder, true) != 0){
					throw Exception($shell->getFirstOutputLine());
				}
			} catch (Exception $e) {
				throw $e;
			}
		}

		if ($group) {
			try {
				$shell = System_ShellExec::getInstance();
				if ($shell->execute(self::CMD_CHOWN, ':' . $group . ' ' . $this->_folder, true) != 0){
					throw new Exception($shell->getFirstOutputLine());
				}
			} catch (Exception $e) {
				throw $e;
			}
		}
	}


	/**
	 * Creates a folder on the system.
	 *
	 * The method will return an error if the folder already exists.
	 *
	 * @param  string  $owner  folder owner
	 * @param  string  $group  folder group
	 * @param  string  $mode  the mode of the folder
	 * @return  void
	 */
	public function create($owner, $group, $mode){
		$this->_logWriter->log('creat '.$owner.' '.$group.' '.$mode ,Zend_Log::DEBUG  );

		clearstatcache(); // PHP caches file stat information... don't let it

		if ($this->exists()){
			throw new Exception($this->_folder);
		}

		try {
			$shell = System_ShellExec::getInstance();
			if ($shell->execute(self::CMD_MKDIR, "-p $this->_folder", true) != 0){
				throw new Exception($shell->getFirstOutputLine());
			}
		} catch (Exception $e) {
			throw $e;
		}

		if ($owner || $group) {
			try {
				$this->chown($owner, $group);
			} catch (Exception $e) {
				throw $e;
			}
		}
		if ($mode) {
			try {
				$this->chmod($mode);
			} catch (Exception $e) {
				throw $e;
			}
		}
	}


	/**
     * Deletes the folder.
     *
     * @param  bool  $ignoreNonEmptyFolder  flag to ignore (use rm -rf) if files are contained within folder
     * @return  void
     */
	public function delete($ignoreNonEmptyFolder = false)	{
		$this->_logWriter->log('delete ' ,Zend_Log::DEBUG  );
		if (! $this->exists()){
			throw new Exception($this->_folder);
		}
		$shell = System_ShellExec::getInstance();
		if ($ignoreNonEmptyFolder) {
			try {
				//TODO TODO TODO - validate the hell out of an "rm -rf"
				if ($shell->execute(self::CMD_RM, "-rf $this->_folder", true) != 0){
					throw new Exception($shell->getFirstOutputLine());
				}
			} catch (Exception $e) {
				throw $e;
			}
		} else {
			try {
				if ($shell->execute(self::CMD_RMDIR, $this->_folder, true) != 0){
					throw new Exception($shell->getFirstOutputLine());
				}
			} catch (Exception $e) {
				throw $e;
			}
		}
	}


	/**
	 * Checks to see if given folder is really a folder.
	 *
	 * @return  boolean  true if directory
	 */
	public function isDirectory(){
		try {
			$shell = new SpotSec_ShellExec();
			if ($shell->execute(self::CMD_FILE, $this->_folder, true) != 0){
				throw new Exception($shell->getFirstOutputLine());
			}
		} catch (Exception $e) {
			throw $e;
		}

		if (preg_match('/directory/', $shell->getFirstOutputLine())){
			return true;
		}	else{
			return false;
		}
	}


	/**
	 * Checks the existence of the folder.
	 *
	 * @return  boolean  true if folder exists
	 */

	public function exists()	{
		if ($this->_superUser) {
			try {
				$shell = System_ShellExec::getInstance();
				if ($shell->execute(self::CMD_LS, $this->_folder, true) != 0){
					return false;
				}
			} catch (Exception $e) {
				throw $e;
			}
			return true;
		} else {
			if (is_dir($this->_folder)){
				return true;
			}
			return false;
		}
	}

	/**
	 * Returns the listing of files in the directory.
	 *
	 * The current (.) and and parent (..) entries are not included.
	 *
	 * @return  array  file listing
	 */
	public function getListing(){
		$this->_logWriter->log('getListing ' ,Zend_Log::DEBUG  );

		$listing = array();

		if (! $this->exists()){
			throw new Exception($this->_folder);
		}
		if ($this->_superUser) {
			try {
				$shell = new SpotSec_ShellExec();
				if ($shell->execute(self::CMD_LS, $this->_folder, true) != 0){
					throw new Exception($shell->getFirstOutputLine());
				}
			} catch (Exception $e) {
				throw $e;
			}
			$fullList = $shell->getOutput();
		} else {
			$fullList = scandir($this->_folder);
		}
		foreach ($fullList as $file) {
			if ($file != '.' && $file != '..')
			$listing[] = $file;
		}
		sort($listing);
		return $listing;
	}

	/**
	 * Returns the octal permissions of the current folder.
	 *
	 * @return string folder permissions
	 * @throws SpotSec_Folder_Exception_NotFound, EngineException
	 */
	public public function getPermissions()	{
		$this->_logWriter->log('getPermissions ' ,Zend_Log::DEBUG  );
		clearstatcache();

		if (! $this->exists()){
			throw new Exception($this->_folder);
		}
		// TODO: this will fail on folders that user webconfig cannot read (protected directories).
		// Added EngineException to docs to futureproof API.
		return substr(sprintf('%o', fileperms($this->_folder)), -4);
	}

	/**
	 * Returns the listing of files in the directory.
	 *
	 * The current (.) and and parent (..) entries are not included.
	 *
	 * @return  array  file listing
	 */
	public function getRecursiveListing()	{
		$this->_logWriter->log('getRecursiveListing ' ,Zend_Log::DEBUG  );

		if (! $this->exists()){
			throw new Exception($this->_folder);
		}

		$listing = array();
		$fullList = array();

		try {
			$shell = System_ShellExec::getInstance();
			if ($shell->execute(self::CMD_FIND, "$this->_folder -type f", true) != 0){
				throw new Exception($shell->getFirstOutputLine());
			}
			$fullList = $shell->getOutput();
		} catch (Exception $e) {
			throw $e;
		}

		foreach ($fullList as $file){
			$listing[] = preg_replace('/' . preg_quote($this->_folder, '/') . '\//', '', $file);
		}
		sort($listing);
		return $listing;
	}

	/**
	 * Returns the foldername (using PHP 'realpath') resolving references like "../../".
	 *
	 * @return  string  name of present working directory
	 */
	public function getFolderName(){
		return realpath($this->_folder);
	}
}
